﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataSource.cs" company="Vlady-mix">
//   Vlady-mix 2105
// </copyright>
// <summary>
//   The day data source.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace CustomDatePicker.Controls.DatePicker
{
    using System;

    #region Public Sealed Class

    /// <summary>
    ///     The day data source.
    /// </summary>
    public sealed class DayDataSource
    {
        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                01,
                relativeDate.Hour,
                relativeDate.Minute,
                relativeDate.Second);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return DateTimeHelper.DaysInMonth(relativeDate.Year, relativeDate.Month);
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            var daysInMonth = DateTimeHelper.DaysInMonth(relativeDate.Year, relativeDate.Month);
            var nextDay = ((daysInMonth + relativeDate.Day - 1 + delta) % daysInMonth) + 1;
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                nextDay,
                relativeDate.Hour,
                relativeDate.Minute,
                relativeDate.Second);
        }

        #endregion
    }

    /// <summary>
    ///     The year data source.
    /// </summary>
    public sealed class YearDataSource
    {
        /// <summary>
        ///     The years availables.
        /// </summary>
        private const int YearsAvailables = 150;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            // DateTime
            // Année bisextile
            if (DateTimeHelper.DaysInMonth(1900, relativeDate.Month)
               < DateTimeHelper.DaysInMonth(relativeDate.Year, relativeDate.Month))
            {
                return new DateTime(1900, relativeDate.Month, DateTimeHelper.DaysInMonth(1900, relativeDate.Month), 0, 0, 0);
            }

            return new DateTime(
                1900,
                relativeDate.Month,
                relativeDate.Day,
                relativeDate.Hour,
                relativeDate.Minute,
                relativeDate.Second);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return YearsAvailables;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            if ((1601 == relativeDate.Year) || (3000 == relativeDate.Year))
            {
                return null;
            }

            // Principe : Incrémenter l'année de 1
            // Attention : Si l'année est Bisextile, alors l'année + delta du mois de février peut ne  pas être bonne !
            var nextYear = relativeDate.Year + delta;

            // Calcul du bon jour
            // Minimum entre le nb de jour aujourd'hui et le nb de jours de l'année d'aprés
            // Si aujourd'hui == 29 et année prochaine == 28 alors return 28
            // si AUjourd'hui == 28 et année prochaine == 29 alors return 28
            var nextDay = Math.Min(originalDate.Day, DateTimeHelper.DaysInMonth(nextYear, relativeDate.Month));

            return new DateTime(
                nextYear,
                relativeDate.Month,
                nextDay,
                relativeDate.Hour,
                relativeDate.Minute,
                relativeDate.Second);
        }

        #endregion
    }

    /// <summary>
    ///     The month data source.
    /// </summary>
    public sealed class MonthDataSource
    {
        /// <summary>
        ///     The months in year.
        /// </summary>
        private const int MonthsInYear = 12;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                01,
                relativeDate.Day,
                relativeDate.Hour,
                relativeDate.Minute,
                relativeDate.Second);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return MonthsInYear;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            // Récupère le mois prochain 
            var nextMonth = ((MonthsInYear + relativeDate.Month - 1 + delta) % MonthsInYear) + 1;

            // Récupère le jours suivant 
            var nextDay = Math.Min(relativeDate.Day, DateTimeHelper.DaysInMonth(relativeDate.Year, nextMonth));

            return new DateTime(
                relativeDate.Year,
                nextMonth,
                nextDay,
                relativeDate.Hour,
                relativeDate.Minute,
                relativeDate.Second);

            /*
            newData = DataSource.GetNext(firstWrapper) as DateTimeWrapper;

            // Jour étalon (31)
            int currentWrapperDay = wrapper.DateTime.Day;

            // Jour max du mois en cours (en Avril 31)
            int currentMaxItemDay = DateTime.DaysInMonth(newData.DateTime.Year, newData.DateTime.Month);

            // A cause du précédent je suis à 30 là
            int currentRecalculatedDay = newData.DateTime.Day;

            // Si le jour en cours 
            if (currentWrapperDay > currentRecalculatedDay && currentWrapperDay <= currentMaxItemDay)
                {
                newData = new DateTimeWrapper(new DateTime(newData.DateTime.Year, newData.DateTime.Month, currentWrapperDay));
                }
            */
        }

        #endregion
    }

    /// <summary>
    ///     The twelve hour data source.
    /// </summary>
    public sealed class TwelveHourDataSource
    {
        /// <summary>
        ///     The hours in half day.
        /// </summary>
        private const int HoursInHalfDay = 24;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                0,
                relativeDate.Minute,
                relativeDate.Second,
                0);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return HoursInHalfDay;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            var nextHour = (HoursInHalfDay + relativeDate.Hour + delta) % HoursInHalfDay;
            nextHour += HoursInHalfDay <= relativeDate.Hour ? HoursInHalfDay : 0;
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                nextHour,
                relativeDate.Minute,
                relativeDate.Second,
                0);
        }

        #endregion
    }

    /// <summary>
    ///     The minute data source.
    /// </summary>
    public sealed class MinuteDataSource
    {
        /// <summary>
        ///     The minutes in hour.
        /// </summary>
        private const int MinutesInHour = 60;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                relativeDate.Hour,
                0,
                relativeDate.Second,
                0);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return MinutesInHour;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            var nextMinute = (MinutesInHour + relativeDate.Minute + delta) % MinutesInHour;
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                relativeDate.Hour,
                nextMinute,
                relativeDate.Second,
                0);
        }

        #endregion
    }

    /// <summary>
    ///     The second data source.
    /// </summary>
    public sealed class SecondDataSource
    {
        /// <summary>
        ///     The seconds in minute.
        /// </summary>
        private const int SecondsInMinute = 60;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                relativeDate.Hour,
                relativeDate.Minute,
                0,
                0);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return SecondsInMinute;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            var nextSecond = (SecondsInMinute + relativeDate.Second + delta) % SecondsInMinute;
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                relativeDate.Hour,
                relativeDate.Minute,
                nextSecond,
                0);
        }

        #endregion
    }

    /// <summary>
    ///     The am pm data source.
    /// </summary>
    public sealed class AmPmDataSource
    {
        /// <summary>
        ///     The hours in day.
        /// </summary>
        private const int HoursInDay = 24;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                0,
                relativeDate.Minute,
                relativeDate.Second,
                0);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return HoursInDay;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            var nextHour = relativeDate.Hour + (delta * (HoursInDay / 2));
            if ((nextHour < 0) || (HoursInDay <= nextHour))
            {
                return null;
            }

            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                nextHour,
                relativeDate.Minute,
                relativeDate.Second,
                0);
        }

        #endregion
    }

    /// <summary>
    ///     The twenty four hour data source.
    /// </summary>
    public sealed class TwentyFourHourDataSource
    {
        /// <summary>
        ///     The hours in day.
        /// </summary>
        private const int HoursInDay = 24;

        #region Public Methods

        /// <summary>
        /// The get first available date.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime GetFirstAvailableDate(DateTime relativeDate)
        {
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                0,
                relativeDate.Minute,
                relativeDate.Second,
                0);
        }

        /// <summary>
        /// The get number of items.
        /// </summary>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public int GetNumberOfItems(DateTime relativeDate)
        {
            return HoursInDay;
        }

        /// <summary>
        /// The get next.
        /// </summary>
        /// <param name="originalDate">
        /// The original date.
        /// </param>
        /// <param name="relativeDate">
        /// The relative date.
        /// </param>
        /// <param name="delta">
        /// The delta.
        /// </param>
        /// <returns>
        /// The <see cref="DateTime"/>.
        /// </returns>
        public DateTime? GetNext(DateTime originalDate, DateTime relativeDate, int delta)
        {
            var nextHour = (HoursInDay + relativeDate.Hour + delta) % HoursInDay;
            return new DateTime(
                relativeDate.Year,
                relativeDate.Month,
                relativeDate.Day,
                nextHour,
                relativeDate.Minute,
                relativeDate.Second,
                0);
        }

        #endregion
    }

    #endregion

    #region Internal Class
    /// <summary>
    ///     The date time helper.
    /// </summary>
    internal class DateTimeHelper
    {
        /// <summary>
        ///     The days to month 365.
        /// </summary>
        internal static readonly int[] DaysToMonth365 =
            {
                0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
            };

        /// <summary>
        ///     The days to month 366.
        /// </summary>
        internal static readonly int[] DaysToMonth366 =
            {
                0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
            };

        #region Public Static Methods

        /// <summary>
        /// The days in month.
        /// </summary>
        /// <param name="year">
        /// The year.
        /// </param>
        /// <param name="month">
        /// The month.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        public static int DaysInMonth(int year, int month)
        {
            if (month < 1 || month > 12)
            {
                throw new ArgumentOutOfRangeException("month");
            }

            var numArray = DateTime.IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;

            return numArray[month] - numArray[month - 1];
        }

        #endregion
    }
    #endregion
}
